use std::time::{Duration, SystemTime};

use anyhow::anyhow;
use salvo::fs::NamedFile;
use salvo::http::Method;
use salvo::prelude::Text;
use salvo::{handler, Depot, Request};
use tokio::io::AsyncWriteExt;
use tracing::{debug, info, warn};

use crate::config::CFG;

static SAVE_PATH: &str = "./public";

#[handler]
pub async fn set_data(depot: &mut Depot) {
    depot.insert("offline", CFG.server.offline);
    depot.insert("cache_time", CFG.server.cache_time);
    depot.insert("registry", CFG.server.registry.clone());
}

#[handler]
pub async fn get_package(req: &mut Request, depot: &mut Depot) -> anyhow::Result<Text<String>> {
    let package: String = req.param("package").ok_or(anyhow!("get params error"))?;
    let file_dir = format!("{}/{}", SAVE_PATH, package);
    let file_path = format!("{}/package.json", file_dir);
    let data;

    let cache_time: usize = depot.get("cache_time").copied().unwrap();
    let registry: String = depot.get::<String>("registry").unwrap().to_string();
    let host = req.header::<String>("Host").unwrap();
    let scheme = get_scheme(req);
    match tokio::fs::try_exists(file_path.as_str()).await?
        && std::fs::metadata(file_path.as_str())?.modified()?
            + Duration::from_secs((60 * cache_time) as u64)
            > SystemTime::now()
    {
        true => {
            data = tokio::fs::read_to_string(file_path.as_str()).await?;
        }
        false => {
            let res = surf::get(format!("{}/{}", registry.as_str(), package.as_str()).as_str())
                .recv_string()
                .await;
            match res {
                Ok(s) => {
                    tokio::fs::create_dir_all(file_dir.as_str()).await?;
                    let mut f = tokio::fs::File::create(file_path.as_str()).await?;
                    f.write_all(s.as_bytes()).await?;
                    f.flush().await?;
                    data = s.clone();
                    info!("load from registry {}", package.as_str());
                }
                Err(_) => {
                    data = tokio::fs::read_to_string(file_path.as_str()).await?;
                    warn!(
                        "registry access error, load from disk: {}",
                        package.as_str()
                    );
                }
            }
        }
    }
    Ok(Text::Json(data.to_string().replace(
        registry.as_str(),
        format!("{}://{}", scheme.as_str(), host.as_str()).as_str(),
    )))
}

#[handler]
pub async fn get_package_version(
    req: &mut Request,
    depot: &mut Depot,
) -> anyhow::Result<Text<String>> {
    let package: String = req.param("package").unwrap();
    let version: String = req.param("version").unwrap();

    let registry: String = depot.get::<String>("registry").unwrap().to_string();
    let host = req.header::<String>("Host").unwrap();
    let scheme = get_scheme(req);

    Ok(Text::Json(
        surf::get(format!("{}/{}/{}", registry.clone(), package.as_str(), version).as_str())
            .recv_string()
            .await
            .map_err(|e| anyhow!("{}", e.to_string()))?
            .replace(
                registry.as_str(),
                format!("{}://{}", scheme.as_str(), host.as_str()).as_str(),
            ),
    ))
}

#[handler]
pub async fn get_package_tgz(req: &mut Request, depot: &mut Depot) -> anyhow::Result<NamedFile> {
    let package: String = req.param("package").unwrap();
    let package_tgz: String = req.param("package_tgz").unwrap();
    let file_dir = format!("{}/{}", SAVE_PATH, package);
    let file_path = format!("{}/{}", file_dir, package_tgz);

    let registry: String = depot.get::<String>("registry").unwrap().to_string();

    match tokio::fs::try_exists(file_path.as_str()).await? {
        true => {}
        false => {
            let res = surf::get(format!("{}/{}/-/{}", registry, package, package_tgz))
                .await
                .map_err(|e| anyhow!("{}", e.to_string()))?
                .body_bytes()
                .await
                .map_err(|e| anyhow!("{}", e.to_string()))?;
            tokio::fs::create_dir_all(file_dir.as_str()).await?;
            let mut f = tokio::fs::File::create(file_path.as_str()).await.unwrap();
            f.write_all(res.as_slice()).await?;
            f.flush().await?;
        }
    }

    Ok(NamedFile::open(file_path.as_str()).await?)
}

#[handler]
pub async fn get_group_package_tgz(
    req: &mut Request,
    depot: &mut Depot,
) -> anyhow::Result<NamedFile> {
    let group: String = req.param("group").unwrap();
    let package: String = req.param("package").unwrap();
    let package_tgz: String = req.param("package_tgz").unwrap();
    let file_dir = format!("{}/{}/{}", SAVE_PATH, group, package);
    let file_path = format!("{}/{}", file_dir, package_tgz);

    let registry: String = depot.get::<String>("registry").unwrap().to_string();

    match tokio::fs::try_exists(file_path.as_str()).await? {
        true => {}
        false => {
            //            let res = surf::get(format!("{}/{}/{}/-/{}", registry, group, package, package_tgz))
            //                .await
            //                .map_err(|e| anyhow!("{}", e.to_string()))?
            //                .body_bytes()
            //                .await
            //                .map_err(|e| anyhow!("{}", e.to_string()))?;
            let mut res = surf::get(format!(
                "{}/{}/{}/-/{}",
                registry, group, package, package_tgz
            ))
            .await
            .map_err(|e| anyhow!("{}", e.to_string()))?;
            if res.status() != 200 {
                return Err(anyhow!("后端下载错误,错误代码:{}", res.status()));
            }
            let body = res
                .body_bytes()
                .await
                .map_err(|e| anyhow!("{}", e.to_string()))?;
            tokio::fs::create_dir_all(file_dir.as_str()).await?;
            let mut f = tokio::fs::File::create(file_path.as_str()).await.unwrap();
            f.write_all(body.as_slice()).await?;
            f.flush().await?;
        }
    }

    Ok(NamedFile::open(file_path.as_str()).await?)
}

#[handler]
pub async fn hoop_test(req: &mut Request) {
    println!("proxy url: {}", req.uri().path());
    println!(
        "proxy count: {}",
        *req.method() != Method::PUT
            && req
                .uri()
                .path()
                .trim_start_matches('/')
                .split("/")
                .filter(|x| { x.len() > 0 })
                .count()
                != 1
    );
    if *req.method() != Method::PUT
        && req
            .uri()
            .path()
            .trim_start_matches('/')
            .split("/")
            .filter(|x| x.len() > 0)
            .count()
            != 1
    {
        return;
    }
    debug!(
        "delete package: {}",
        std::path::Path::new(SAVE_PATH)
            .join(req.uri().path().trim_start_matches('/').replace("%2f", "/"))
            .to_string_lossy()
            .to_string()
    );
    tokio::fs::remove_dir_all(
        std::path::Path::new(SAVE_PATH)
            .join(req.uri().path().trim_start_matches('/').replace("%2f", "/")),
    )
    .await
    .ok();
}

fn get_scheme(req: &Request) -> String {
    req.header::<String>("X-Forwarded-Proto")
        .unwrap_or_else(|| req.scheme().to_string())
}

#[test]
fn test_method() {
    assert_eq!(&Method::PUT, Method::PUT)
}
